package com.wjc.train.business.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wjc.train.business.entity.SkToken;
import com.wjc.train.business.repository.SkTokenRepository;
import com.wjc.train.business.req.SkTokenQueryReq;
import com.wjc.train.business.req.SkTokenSaveReq;
import com.wjc.train.business.resp.SkTokenQueryResp;
import com.wjc.train.business.service.IDailyTrainSeatService;
import com.wjc.train.business.service.IDailyTrainStationService;
import com.wjc.train.business.service.ISkTokenService;
import com.wjc.train.common.enums.RedisKeyPreEnum;
import com.wjc.train.common.enums.LogicEnum;
import com.wjc.train.common.exception.BusinessException;
import com.wjc.train.common.exception.BusinessExceptionCode;
import com.wjc.train.common.untils.CopyUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author Guessed
 * @since 2023-09-01
 */
@Service
@Slf4j
public class SkTokenServiceImpl  implements ISkTokenService {

    @Resource
    private SkTokenRepository sktokenRepository;

    @Resource
    private IDailyTrainSeatService dailyTrainSeatService;

    @Resource
    private IDailyTrainStationService dailytrainstationService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    @Value("${spring.profiles.active:dev}")
    private String env;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(SkTokenSaveReq sktokenSaveReq) {
        SkToken sktoken = CopyUtil.copy(sktokenSaveReq, SkToken.class);
        sktokenRepository.saveOrUpdate(sktoken);
    }

    @Override
    public IPage<SkTokenQueryResp> queryList(IPage<SkTokenQueryResp> page, SkTokenQueryReq sktokenQueryReq) {
        return sktokenRepository.queryList(page,sktokenQueryReq);
    }

    @Override
    public void delete(Long id) {
        sktokenRepository.getBaseMapper().deleteById(id);
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean vaildateSkToken(Date trainDate, String trainCode,Long memberId) {
        log.info("会员: {},获取日期:{},车次:{} 的令牌开始",memberId,DateUtil.formatDate(trainDate),trainCode);

        if (!"dev".equals(env)) {
            //上锁，防重复请求
            String lockKey = RedisKeyPreEnum.SK_TOKEN + "-" + DateUtil.formatDate(trainDate) + "-" + trainCode + "-" + memberId;
            RLock rLock = redissonClient.getLock(lockKey);
            try {
                boolean tryLock = rLock.tryLock(1L, 5L, TimeUnit.SECONDS);
                if (!tryLock) {
                    log.info("当前用户请求过于频繁");
                    throw new BusinessException(BusinessExceptionCode.CONFIRM_ORDER_SK_FRE);
                }
            } catch (InterruptedException e) {
                log.error("令牌锁被中断!");
                throw new RuntimeException(e);
            }
        }

        //读取令牌缓存
        String skTokenCountKey  = RedisKeyPreEnum.SK_TOKEN_COUNT +"-" + DateUtil.formatDate(trainDate) + "-" + trainCode;
        String keyValue = stringRedisTemplate.opsForValue().get(skTokenCountKey);
        if(StringUtils.isNotBlank(keyValue)){
            log.info("缓存中有令牌数量: {}",keyValue);
            Long count = stringRedisTemplate.opsForValue().decrement(skTokenCountKey, 1);
            if(count < 0L){
                log.error("令牌数小于0");
                return false;
            }else {
                log.info("扣减令牌后,余量:{}",count);
                //更新过期时间
                stringRedisTemplate.expire(skTokenCountKey,60,TimeUnit.SECONDS);
                //每获取5个令牌更新一次数据库
                if(count % 5 == 0){
                    sktokenRepository.decrease(trainDate, trainCode,5);
                }
                return true;
            }
        }else {
            log.info("缓存中没有该车次的令牌数:{}",skTokenCountKey);
            SkToken dBskToken = sktokenRepository.getOne(new QueryWrapper<SkToken>()
                    .eq("train_code", trainCode)
                    .eq("train_date", trainDate)
                    .eq("is_deleted", LogicEnum.NOT_DELETED.getCode()));
            if(ObjectUtils.isEmpty(dBskToken)){
                log.info("找不到日期:{} 车次:{} 的令牌记录",trainDate,trainCode);
                return false;
            }
            Integer tokenCount = dBskToken.getTokenCount();
            if(tokenCount <= 0){
                log.info("找不到日期:{} 车次:{} 的令牌记录为0",trainDate,trainCode);
                return false;
            }
            //令牌还有余量
            //余量-1
            int newCount = tokenCount - 1;
            dBskToken.setTokenCount(newCount);
            log.info("将该车次令牌数放入缓存key: {},val:{}",skTokenCountKey,newCount);
            stringRedisTemplate.opsForValue().set(skTokenCountKey,String.valueOf(newCount), Duration.ofSeconds(60));
            return true;
        }
    }

    @Override
    public void genDaily(Date trainDate, String trainCode) {
        log.info("开始生成日期:{},车次:{}的令牌信息", DateUtil.formatDate(trainDate),trainCode);
        List<SkToken> skTokenList = sktokenRepository.lambdaQuery().eq(SkToken::getTrainCode, trainCode)
                .eq(SkToken::getTrainDate, trainDate)
                .eq(SkToken::getIsDeleted, LogicEnum.NOT_DELETED.getCode())
                .list();
        if(CollectionUtil.isNotEmpty(skTokenList)){
            List<Long> collect = skTokenList.stream().map(SkToken::getId).toList();
            sktokenRepository.getBaseMapper().deleteBatchIds(collect);
        }

        int countSeat = dailyTrainSeatService.countSeat(trainDate, trainCode);
        log.info("车次[{}] 座位数:{}",trainCode,countSeat);

        long stationCount = dailytrainstationService.countByTrainCode(trainDate,trainCode);
        log.info("车次[{}] 站数:{}",trainCode,stationCount);

        //根据实际卖票比例来定，一趟火车最多可以卖 countSeat* stationCount张票
        int count = (int) (stationCount * countSeat);
        log.info("车次[{}] 初始生成令牌数:{}",trainCode,count);

        SkToken skToken = new SkToken();
        skToken.setTrainDate(trainDate);
        skToken.setTrainCode(trainCode);
        skToken.setTokenCount(count);
        sktokenRepository.save(skToken);

    }
}
